package ogs.lib.mina.core.filterchain
{
	import flash.net.FileFilter;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	
	import mx.utils.ObjectProxy;
	
	import org.osflash.thunderbolt.Logger;
	
	import ogs.lib.mina.core.service.IoConnector;
	
	public class DefaultIoFilterChain implements IoFilterChain
	{
		private var connector:IoConnector = null;
		
		private var name2entry:Dictionary = new Dictionary();// Map<String, Entry> 

		private var head:IoFilterChainNodeImpl;
		
		private var tail:IoFilterChainNodeImpl;

		
		public function DefaultIoFilterChain(connector:IoConnector)
		{
			if (connector == null) {
				throw new ArgumentError("connector");
			}
			
			this.connector = connector;
			head = new IoFilterChainNodeImpl(this, null, null, "head", new HeadFilter(connector));
			tail = new IoFilterChainNodeImpl(this, head, null, "tail", new TailFilter(connector));
			head.nextNode = tail;
		}
		
		public function getConnector():IoConnector
		{
			return this.connector;
		}
		
		public function getFilterChainNode(filter:IoFilter):IoFilterChainNode
		{
			var node:IoFilterChainNodeImpl = head.nextNode;
			while (node != tail)
			{
				if (node.getFilter() == filter) {
					return node;
				}
				node = node.nextNode;
			}
			return null;
		}
		
		public function getFilterChainNodeByName(name:String):IoFilterChainNode
		{
			var node:IoFilterChainNode = name2entry[name];
			if (!node){
				return null;
			}
			return node;
		}
		
		public function getFilterChainNodeByType(filterType:Class):IoFilterChainNode
		{
			var node:IoFilterChainNodeImpl = head.nextNode;
			while (node != tail) {
				if (node.getFilter() is filterType) {
					return node;
				}
				node = node.nextNode;
			}
			return null;
		}
		
		public function getFilterByName(name:String):IoFilter
		{
			var node:IoFilterChainNode = getFilterChainNodeByName(name);
			if (node == null) {
				return null;
			}
			return node.getFilter();
		}
		
		public function getFilterByType(filterType:Class):IoFilter
		{
			var node:IoFilterChainNode = getFilterChainNodeByType(filterType);
			if (node == null) {
				return null;
			}
			return node.getFilter();
		}
		
		public function getNextFilter(filter:IoFilter):NextFilter
		{
			var node:IoFilterChainNode = getFilterChainNode(filter);
			if (node == null) {
				return null;
			}
			return node.getNextFilter();
		}
		
		public function getNextFilterByName(name:String):NextFilter
		{
			var node:IoFilterChainNode = getFilterChainNodeByName(name);
			if (node == null) {
				return null;
			}
			return node.getNextFilter();
		}
		
		public function getNextFilterByType(filterType:Class):NextFilter
		{
			var node:IoFilterChainNode = getFilterChainNodeByType(filterType);
			if (node == null) {
				return null;
			}
			return node.getNextFilter();
		}
		
		public function getAll():Array
		{
			var list:Array = [];
			var node:IoFilterChainNodeImpl = head.nextNode;
			while (node != tail) {
				list.push(node);
				node = node.nextNode;
			}
			return list;
		}
		
		public function getAllReversed():Array
		{
			var list:Array = [];
			var node:IoFilterChainNodeImpl = tail.prevNode;
			while (node != head) {
				list.push(node);
				node = node.prevNode;
			}
			return list;
		}
		
		public function contains(filter:IoFilter):Boolean
		{
			return getFilterChainNode(filter) != null;
		}
		
		public function containsByName(name:String):Boolean
		{
			return getFilterChainNodeByName(name) != null;
		}
		
		public function containsByType(filterType:Class):Boolean
		{
			return getFilterChainNodeByType(filterType) != null;
		}
		
		public function addFirst(name:String, filter:IoFilter):void
		{
			checkAddable(name);
			register(head, name, filter);
		}
		
		public function addLast(name:String, filter:IoFilter):void
		{
			checkAddable(name);
			register(tail.prevNode, name, filter);
		}
		
		public function addBefore(baseName:String, name:String, filter:IoFilter):void
		{
			var baseNode:IoFilterChainNodeImpl = checkOldName(baseName);
			checkAddable(name);
			register(baseNode.prevNode, name, filter);
		}
		
		public function addAfter(baseName:String, name:String, filter:IoFilter):void
		{
			var baseNode:IoFilterChainNodeImpl = checkOldName(baseName);
			checkAddable(name);
			register(baseNode, name, filter);
		}
		
		public function replace(oldFilter:IoFilter, newFilter:IoFilter):void
		{
			var node:IoFilterChainNodeImpl = head.nextNode;
			while (node != tail) {
				if (node.getFilter() == oldFilter) {
					node.setFilter(newFilter);
					return;
				}
				node = node.nextNode;
			}
			throw new ArgumentError("Filter not found: " + getQualifiedClassName(oldFilter));
		}
		
		public function replaceByName(name:String, newFilter:IoFilter):IoFilter
		{
			var node:IoFilterChainNodeImpl = checkOldName(name);
			var oldFilter:IoFilter = node.getFilter();
			node.setFilter(newFilter);
			return oldFilter;
		}
		
		public function replaceByType(oldFilterType:Class, newFilter:IoFilter):IoFilter
		{
			var node:IoFilterChainNodeImpl = head.nextNode;
			while (node != tail) {
				if (node.getFilter() is oldFilterType) {
					var oldFilter:IoFilter = node.getFilter();
					node.setFilter(newFilter);
					return oldFilter;
				}
				node = node.nextNode;
			}
			throw new ArgumentError("Filter not found: " + getQualifiedClassName(oldFilterType));
		}
		
		public function remove(filter:IoFilter):void
		{
			var node:IoFilterChainNodeImpl = head.nextNode;
			while (node != tail) {
				if (node.getFilter() == filter) {
					deregister(node);
					return;
				}
				node = node.nextNode;
			}
			throw new ArgumentError("Filter not found: " + getQualifiedClassName(filter));
		}
		
		public function removeByName(name:String):IoFilter
		{
			var node:IoFilterChainNodeImpl = checkOldName(name);
			deregister(node);
			return node.getFilter();
		}
		
		public function removeByType(filterType:Class):IoFilter
		{
			var node:IoFilterChainNodeImpl = head.nextNode;
			while (node != tail) {
				if (node.getFilter() is filterType) {
					var oldFilter:IoFilter = node.getFilter();
					deregister(node);
					return oldFilter;
				}
				node = node.nextNode;
			}
			throw new ArgumentError("Filter not found: " + getQualifiedClassName(filterType));
		}
		
		public function clear():void
		{
			for each (var node:IoFilterChainNode in name2entry)
			{
				deregister(IoFilterChainNodeImpl(node));
			}
		}
		
		private function register(prevNode:IoFilterChainNodeImpl, name:String, filter:IoFilter):void
		{
			var newNode:IoFilterChainNodeImpl = new IoFilterChainNodeImpl(this, prevNode, prevNode.nextNode,
				name, filter);
			
			filter.onPreAdd(this, name, newNode.getNextFilter());
			
			prevNode.nextNode.prevNode = newNode;
			prevNode.nextNode = newNode;
			name2entry.put(name, newNode);
			
			filter.onPostAdd(this, name, newNode.getNextFilter());
		}

		private function deregister(node:IoFilterChainNodeImpl):void
		{
			var filter:IoFilter = node.getFilter();
			
			filter.onPreRemove(this, node.getName(), node.getNextFilter());
			
			deregister0(node);
			
			filter.onPostRemove(this, node.getName(), node.getNextFilter());
		}
		
		private function deregister0(node:IoFilterChainNodeImpl):void
		{
			var prevNode:IoFilterChainNodeImpl = node.prevNode;
			var nextNode:IoFilterChainNodeImpl = node.nextNode;
			prevNode.nextNode = nextNode;
			nextNode.prevNode = prevNode;
			
			delete name2entry[node.getName()];
		}

		private function checkOldName(baseName:String):IoFilterChainNodeImpl
		{
			var node:IoFilterChainNodeImpl = IoFilterChainNodeImpl(name2entry[baseName]);
			if (node == null) {
				throw new ArgumentError("Filter not found:" + baseName);
			}
			return node;
		}
		
		private function checkAddable(name:String):void
		{
			if (name2entry.hasOwnProperty(name)) {
				throw new ArgumentError("Other filter is using the same name '" + name + "'");
			}
		}
		
		public function fireSessionOpened():void
		{
			var head:IoFilterChainNode = this.head;
			callNextSessionOpened(head);
		}
		
		internal function callNextSessionOpened(node:IoFilterChainNode):void
		{
			try
			{
				var filter:IoFilter = node.getFilter();
				var nextFilter:NextFilter = node.getNextFilter();
				filter.sessionOpened(nextFilter);
			} catch (e:Error) {
				fireExceptionCaught(e);
			}
		}
		
		public function fireSessionClosed():void
		{
			var head:IoFilterChainNode = this.head;
			callNextSessionClosed(head);
		}
		
		internal function callNextSessionClosed(node:IoFilterChainNode):void
		{
			try
			{
				var filter:IoFilter = node.getFilter();
				var nextFilter:NextFilter = node.getNextFilter();
				filter.sessionClosed(nextFilter);
			} catch (e:Error) {
				fireExceptionCaught(e);
			}
		}
		
		public function fireMessageReceived(message:Object):void
		{
			var head:IoFilterChainNode = this.head;
			callNextMessageReceived(head, message);
		}
		
		internal function callNextMessageReceived(node:IoFilterChainNode, message:Object):void
		{
			try
			{
				var filter:IoFilter = node.getFilter();
				var nextFilter:NextFilter = node.getNextFilter();
				filter.messageReceived(nextFilter, message);
			} catch (e:Error) {
				fireExceptionCaught(e);
			}
		}
		
		public function fireExceptionCaught(cause:Error):void
		{
			var head:IoFilterChainNode = this.head;
			callNextMessageReceived(head, cause);
		}
		
		internal function callNextExceptionCaught(node:IoFilterChainNode, cause:Error):void
		{
			try 
			{
				var filter:IoFilter = node.getFilter();
				var nextFilter:NextFilter = node.getNextFilter();
				filter.exceptionCaught(nextFilter, cause);
			} catch (e:Error) {
				Logger.warn("Unexpected exception from exceptionCaught handler.", e);
			}
		}
		
		public function fireFilterWrite(message:Object):void
		{
			var tail:IoFilterChainNode = this.tail;
			callPreviousFilterWrite(tail, message);
		}
		
		internal function callPreviousFilterWrite(node:IoFilterChainNode, message:Object):void
		{
			try {
				var filter:IoFilter = node.getFilter();
				var nextFilter:NextFilter = node.getNextFilter();
				filter.filterWrite(nextFilter, message);
			} catch (e:Error) {
				fireExceptionCaught(e);
			}
		}
		
		public function fireFilterClose():void
		{
			var tail:IoFilterChainNode = this.tail;
			callPreviousFilterClose(tail);
		}

		internal function callPreviousFilterClose(node:IoFilterChainNode):void 
		{
			try {
				var filter:IoFilter = node.getFilter();
				var nextFilter:NextFilter = node.getNextFilter();
				filter.filterClose(nextFilter);
			} catch (e:Error) {
				fireExceptionCaught(e);
			}
		}

	}
}